package com.wchat.network.message;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.nio.ByteBuffer;

import org.simpleframework.xml.core.Persister;

import android.util.Log;

import com.wchat.network.Configs;
import com.wchat.network.Controller;
import com.wchat.network.device.Device;
import com.wchat.network.message.file.AcceptFileMessage;
import com.wchat.network.message.file.FileInfo;
import com.wchat.network.message.file.FileMessage;
import com.wchat.network.message.file.FileReadyMessage;
import com.wchat.network.message.file.ImageMessage;
import com.wchat.network.message.file.RejectFileMessage;
import com.wchat.network.message.file.RequestFileMessage;
import com.wchat.network.message.share.ShareDirectory;

/**
 * byte[0]: message type
 */
public abstract class Message implements Serializable {

	private static final long serialVersionUID = -5330724791705702486L;

	public static final byte TYPE_WHOAMI = 0x01;
	public static final byte TYPE_TEXT = 0x02;
	/*
	 * request other device to send me WhoAmI message
	 */
	public static final byte TYPE_DETECT = 0x03;
	public static final byte TYPE_FILE = 0x04;
	public static final byte TYPE_REJECT_FILE = 0x05;
	public static final byte TYPE_ACCEPT_FILE = 0x06;
	/*
	 * notify receive than server socket is ready
	 */
	public static final byte TYPE_FILE_READY = 0x07;
	public static final byte TYPE_IMAGE = 0x08;
	public static final byte TYPE_LEAVE_GROUP = 0x09;
	public static final byte TYPE_GOODBYE = 0x0A;
	public static final byte TYPE_GET_SHARE_DIRECTORY = 0x0B;
	public static final byte TYPE_SHARE_DIRECTORY = 0x0C;
	public static final byte TYPE_REQUEST_FILE = 0x0D;

	public static final long GROUP_BROADCAST = -1;
	public static final long GROUP_ONE2ONE = 0;
	public static final long GROUP_SHARE = -2;

	protected byte[] bytes;

	protected InetAddress from;

	private long deviceId;

	private long group;
	
	private long receiveTimestamp;

	protected Message() {
		deviceId = Controller.getInstance().getLocalDevice().getId();
	}

	public abstract byte getType();

	public byte[] getBytes() {
		return bytes;
	}

	public InetAddress getFrom() {
		return from;
	}

	public void setFrom(InetAddress from) {
		this.from = from;
	}

	public long getDeviceId() {
		return deviceId;
	}

	public void setDeviceId(long deviceId) {
		this.deviceId = deviceId;
	}

	public long getGroup() {
		return group;
	}

	public void setGroup(long group) {
		this.group = group;
	}

	public long getReceiveTimestamp() {
		return receiveTimestamp;
	}

	public void setReceiveTimestamp(long receiveTimestamp) {
		this.receiveTimestamp = receiveTimestamp;
	}

	public static Message parserMessage(DatagramPacket packet) throws MessageException {
		byte[] bytes = packet.getData();
		int index = 0;
		/* header: 4 */
		final byte[] messageHeader = Configs.MESSAGE_HEADER;
		for (int i = 0; i < messageHeader.length; i++) {
			if (bytes[index++] != messageHeader[i]) {
				throw new MessageException("invalid message header");
			}
		}
		/* type: 1 */
		int type = bytes[index++];
		/* device id: 8 */
		byte[] deviceIdBytes = new byte[8];
		for (int i = 0; i < 8; i++) {
			deviceIdBytes[i] = bytes[index++];
		}
		long deviceId = bytearray2long(deviceIdBytes);
		/* group info: 8 */
		byte[] groupBytes = new byte[8];
		for (int i = 0; i < 8; i++) {
			groupBytes[i] = bytes[index++];
		}
		long group = bytearray2long(groupBytes);
		/* length: 4 */
		byte[] lengthBytes = new byte[4];
		for (int i = 0; i < 4; i++) {
			lengthBytes[i] = bytes[index++];
		}
		int length = bytearray2int(lengthBytes);
		if (type != TYPE_WHOAMI) {
			Configs.i("message", "GET: ");
			Configs.i("message", "xml byte length: " + length);
		}
		String text = new String(bytes, index, length);
		if (type != TYPE_WHOAMI) {
			Configs.i("message", text);
		}

		Persister p = new Persister();
		Message message = null;
		try {
			switch (type) {
			case TYPE_TEXT:
				message = p.read(TextMessage.class, text);
				break;
			case TYPE_WHOAMI:
				message = p.read(WhoAmIMessage.class, text);
				break;
			case TYPE_DETECT:
				message = p.read(DetectMessage.class, text);
				break;
			case TYPE_FILE:
				message = p.read(FileMessage.class, text);
				break;
			case TYPE_REJECT_FILE:
				message = p.read(RejectFileMessage.class, text);
				break;
			case TYPE_ACCEPT_FILE:
				message = p.read(AcceptFileMessage.class, text);
				break;
			case TYPE_FILE_READY:
				message = p.read(FileReadyMessage.class, text);
				break;
			case TYPE_IMAGE:
				message = p.read(ImageMessage.class, text);
				break;
			case TYPE_LEAVE_GROUP:
				message = p.read(LeaveGroupMessage.class, text);
				break;
			case TYPE_GOODBYE:
				message = p.read(GoodbyteMessage.class, text);
				break;
			case TYPE_GET_SHARE_DIRECTORY:
				message = p.read(GetShareDirectoryMessage.class, text);
				break;
			case TYPE_SHARE_DIRECTORY:
				message = p.read(ShareDirectoryMessage.class, text);
				break;
			case TYPE_REQUEST_FILE:
				message = p.read(RequestFileMessage.class, text);
			default:
				break;
			}

		} catch (Exception e) {
			throw new MessageException(e);
		}

		if (message != null) {
			message.setDeviceId(deviceId);
			message.setGroup(group);
			return message;
		}
		throw new MessageException("unknown message type");
	}

	public static WhoAmIMessage createWhoAmIMessage(Device device) {
		WhoAmIMessage message = new WhoAmIMessage(device);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static GoodbyteMessage createGoodbyeMessage(Device device) {
		GoodbyteMessage message = new GoodbyteMessage();
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static TextMessage createTextMessage(String text, long group) {
		TextMessage message = new TextMessage(text);
		message.setGroup(group);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static DetectMessage createDetectMessage() {
		DetectMessage message = new DetectMessage();
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static FileMessage createFileMessage(File file, long group) {
		FileMessage message = new FileMessage();
		message.setGroup(group);
		message.setFile(file);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static ImageMessage createImageMessage(File file, long group) {
		ImageMessage message = new ImageMessage();
		message.setGroup(group);
		message.setFile(file);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static AcceptFileMessage createAcceptFileMessage(FileMessage fileMessage) {
		AcceptFileMessage message = new AcceptFileMessage();
		message.setGroup(fileMessage.getGroup());
		message.setOriginalMessage(fileMessage);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static RejectFileMessage createRejectFileMessage(FileMessage fileMessage) {
		RejectFileMessage message = new RejectFileMessage();
		message.setGroup(fileMessage.getGroup());
		message.setOriginalMessage(fileMessage);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static FileReadyMessage createFileReadyMessage(AcceptFileMessage acceptFileMessage, int localPort) {
		FileReadyMessage message = new FileReadyMessage();
		message.setGroup(acceptFileMessage.getGroup());
		message.setOriginalMessage(acceptFileMessage.getOriginalMessage());
		message.setDataPort(localPort);
		message.bytes = getMessageBytes(message);
		return message;
	}

	public static LeaveGroupMessage createLeaveGroupMessage(long groupId) {
		LeaveGroupMessage message = new LeaveGroupMessage();
		message.setLeaveGroupId(groupId);
		message.bytes = getMessageBytes(message);
		return message;
	}
	
	public static GetShareDirectoryMessage createGetShareDirectoryMessage() {
		GetShareDirectoryMessage message = new GetShareDirectoryMessage();
		message.bytes = getMessageBytes(message);
		return message;
	}
	
	public static ShareDirectoryMessage createShareDirectoryMessage(ShareDirectory directory) {
		ShareDirectoryMessage message = new ShareDirectoryMessage();
		message.setShareDirectory(directory);
		message.bytes = getMessageBytes(message);
		return message;
	}
	
	public static RequestFileMessage createRequestFileMessage(FileInfo info) {
		RequestFileMessage message = new RequestFileMessage();
		message.setInfo(info);
		message.bytes = getMessageBytes(message);
		return message;
	}

	private static int _setupMessageHeader(byte[] bytes, int index) {
		final byte[] messageHeader = Configs.MESSAGE_HEADER;
		int length = messageHeader.length;
		for (int i = 0; i < length; i++) {
			bytes[index++] = messageHeader[i];
		}
		return length;
	}

	private static int _setupMessageLongValue(byte[] bytes, long value, int index) {
		byte[] groupBytes = long2bytearray(value);
		int length = groupBytes.length;
		for (int i = 0; i < length; i++) {
			bytes[index++] = groupBytes[i];
		}
		return length;
	}

	private static int _setupMessageType(byte[] bytes, byte type, int index) {
		bytes[index] = type;
		return 1;
	}

	private static int _setupMessageLength(byte[] bytes, int length, int index) {
		int headLength = 4 + 1 + 4 + 8 + 8;
		if (length > Configs.UDP_PACKET_LENGTH - headLength)
			length = Configs.UDP_PACKET_LENGTH - headLength;
		byte[] lenghtBytes = int2bytearray(length);
		for (int i = 0; i < lenghtBytes.length; i++) {
			bytes[index++] = lenghtBytes[i];
		}
		return 4;
	}

	protected static byte[] getMessageBytes(Message message) {
		byte[] bytes = new byte[Configs.UDP_PACKET_LENGTH];
		int index = 0;
		/* header: 4 */
		index += _setupMessageHeader(bytes, index);
		/* type: 1 */
		index += _setupMessageType(bytes, message.getType(), index);
		/* device id: 8 */
		index += _setupMessageLongValue(bytes, message.getDeviceId(), index);
		/* group info: 8 */
		index += _setupMessageLongValue(bytes, message.getGroup(), index);

		Persister p = new Persister();
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			p.write(message, os);
		} catch (Exception e) {
			Log.e("", "", e);
		}

		/* length: 4 */
		int length = os.size();

		if (message.getType() != TYPE_WHOAMI) {
			Configs.i("message", "SEND: ");
			Configs.i("message", os.toString());
			Configs.i("message", "xml byte length: " + length);
		}

		index += _setupMessageLength(bytes, length, index);
		byte[] contentBytes = os.toByteArray();
		for (int i = 0; i < length; i++) {
			bytes[index++] = contentBytes[i];
		}
		return bytes;
	}

	public static byte[] long2bytearray(long l) {
		byte b[] = new byte[8];
		ByteBuffer buf = ByteBuffer.wrap(b);
		buf.putLong(l);
		return b;
	}

	public static byte[] int2bytearray(int i) {
		byte b[] = new byte[4];
		ByteBuffer buf = ByteBuffer.wrap(b);
		buf.putInt(i);
		return b;
	}

	public static long bytearray2long(byte[] b) {
		ByteBuffer buf = ByteBuffer.wrap(b);
		return buf.getLong();
	}

	public static int bytearray2int(byte[] b) {
		ByteBuffer buf = ByteBuffer.wrap(b);
		return buf.getInt();
	}

}
